#include <string.h>

#include "base/Simulator.h"
#include "util/Logger.h"
#include "util/Pool.h"

/*
  global state about pools for statistics reporting
*/
Pool* Pool::Pools[MAX_POOLS];
uint Pool::PoolIndex = 0;
Logger* Pool::PoolLog = NULL;
bool Pool::shutdown_asserts = true;

bool Pool::initializedClass = false;
void Pool::initializeClass () {
    if (initializedClass) {
        return;
    }
    if (global_sim == NULL) {
        return;
    }
    initializedClass = true;
    
    // save this now for use in shutdown, which is after global_sim is deleted
    shutdown_asserts = global_sim->cfg.shutdown_asserts;
}

void* Pool::operator new (size_t size, const char* file, int line)
{
    ASSERT(size == sizeof(Pool));
    return CALLOC(1, size);
}

void* Pool::operator new (size_t size, void* ptr, const char* file, int line)
{
    ASSERT(size == sizeof(Pool));
    return ptr;
}

void Pool::operator delete (void* mem, size_t size)
{
    ASSERT(mem != NULL);
    ASSERT(size == sizeof(Pool));
    FREE(mem);
}

void* Pool::operator new[] (size_t size, const char* file, int line)
{
    return CALLOC(1, size);
}

void Pool::operator delete[] (void* mem, size_t size)
{
    FREE(mem);
}

Pool::Pool (const char*  _name,
            size_t       _size,
            int          _max,
            size_t       _padding,
            bool         _large)
    : name(_name),
      // we need at least a word to make sure we have room for the
      // next pointer in the free list
      size(_size < sizeof(void*) ? sizeof(void*) : _size),
      padding(_padding),
      large(_large),
      freePtr(NULL),
      freeCount(0),
      freeMax(_max),
      total_new(0),
      total_delete(0),
      currently_allocated(0),
      peak_allocated(0),
      total_calloc(0),
      total_free(0),
      total_recycled(0),
      tracing(false),
      nextWarningLevel(_max)
{
    initializeClass();

    ASSERT(_name != NULL);
    
    // don't currently support mixing padding with large allocations
    ASSERT(padding == 0 || !large);

    // padding needs to be a power of 2 for alignment
    ASSERT(padding == 0 || ((padding & (padding-1)) == 0));

    // padding needs to be big enough to hold a void* if present
    ASSERT(padding == 0 || padding >= sizeof(void*));

    Pools[PoolIndex] = this;
    PoolIndex++;
    ASSERT(PoolIndex <= MAX_POOLS);

#if 0
    if (strstr(name, "Flags")) {
        tracing = true;
    }
#endif
}

void Pool::log () {
    ASSERT(PoolLog != NULL);
    PoolLog->i("Pool statistics for %s:\n",   name);
    PoolLog->i("\t total_new           = %lld\n", total_new);
    PoolLog->i("\t total_delete        = %lld\n", total_delete);
    PoolLog->i("\t currently_allocated = %lld\n", currently_allocated);
    PoolLog->i("\t peak_allocated      = %lld\n", peak_allocated);
    PoolLog->i("\t total_calloc        = %lld\n", total_calloc);
    PoolLog->i("\t total_free          = %lld\n", total_free);
    PoolLog->i("\t total_recycled      = %lld\n", total_recycled);
    PoolLog->i("\t total_calloc_large  = %lld\n", total_calloc_large);
    PoolLog->i("\t total_free_large    = %lld\n", total_free_large);
}

Pool::~Pool () {
    if (this != Logger::pool) {
        log();
    }
    if (shutdown_asserts) {
        if (total_new != total_delete) {
            PoolLog->e("For class %s, total_new(%lld) != total_delete(%lld), with currently_allocated = %lld\n", 
                       name, total_new, total_delete, currently_allocated);
        }
        else {
            ASSERT(currently_allocated == 0);
        }
        if (total_calloc_large != total_free_large) {
            PoolLog->e("For class %s, total_calloc_large(%lld) != total_free_large(%lld), with currently_allocated = %lld\n", 
                       name, total_calloc_large, total_free_large, currently_allocated);
        }
    }

    while (freeCount) {
        total_free++;
        dealloc(freePop());
    }
    if (shutdown_asserts) {
        if (total_calloc != total_free) {
            PoolLog->e("For class %s, total_calloc(%lld) != total_free(%lld), with currently_allocated = %lld\n", 
                       name, total_calloc, total_free, currently_allocated);
        }
    }
}

void* Pool::calloc (size_t s, const char* file, int line) {
    total_new++;                              
    currently_allocated++;                    
    if (currently_allocated > peak_allocated) {
        peak_allocated = currently_allocated; 
        if (peak_allocated > nextWarningLevel) {
            Log->d("%lli:For class %s, peak allocation reached %lld while serving %s:%d\n",
                   global_sim != NULL ? (long long int)global_sim->getSimclock() : -1,
                   name,
                   peak_allocated,
                   file,
                   line);           
            nextWarningLevel += WARNING_MSG_STRIDE;
        }
    }
    if (s > size) {
        if (large) {
            total_calloc_large++;
            void *p = CALLOC(1, s);
            if (tracing) {
                Log->d("For class %s, allocating %p of size %d for %s:%d\n", name, p, s, file, line);
            }
            return p;
        }
        Log->e("For class %s, s=%ld should be less than size=%ld\n", name, (long)s, (long)size);
    }

    if (freeCount > 0) {
        total_recycled++;
        void *p = freePop();
        if (tracing) {
            Log->d("For class %s, allocating %p of size %d for %s:%d\n", name, p, s, file, line);
        }
        return p;
    }
    total_calloc++;
    if (padding == 0) { 
        void* p = CALLOC(1, size);
        if (tracing) {
            Log->d("For class %s, allocating %p of size %d for %s:%d\n", name, p, s, file, line);
        }
        return p;
    }
    
    // 2x because padding amount is added to each side
    size_t total = size+(2*padding);
    void* unalignedPtr = CALLOC(1, total);
    uint unaligned = (uint)unalignedPtr;
    uint alignment_mask = (padding-1) ^ 0xffffffff;
    uint aligned = (unaligned+padding) & alignment_mask;
    ASSERT(aligned > unaligned);
    ASSERT(aligned+size <= unaligned+total);
    uint32* alignedPtr = (uint32*)aligned;
    // store unaligned pointer in word before aligned pointer we
    // return so we can find it for deallocation
    alignedPtr[-1] = unaligned;
    if (tracing) {
        Log->d("For class %s, allocating aligned %p of size %d from unaligned %p of size %d for %s:%d\n", 
               name, alignedPtr, size, unalignedPtr, total, file, line);
    }
    return alignedPtr;
}

void Pool::free (void* mem, size_t s) {
    if (tracing) {
        Log->d("For class %s, freeing %p\n", name, mem);
    }
    ASSERT(mem != NULL);
    total_delete++;
    currently_allocated--;
    if (total_delete > total_new) {
        Log->e("For class %s, total_delete(%lld) > total_new(%lld) , with mem=%p and s=%ld\n", 
               name, total_delete, total_new, mem, (long)s);
    }
    if (s > size) {
        ASSERT(large);
        total_free_large++;
        FREE(mem);
        return;
    }
    if (freeCount < freeMax) {
        freePush(mem);
        return;
    }
    total_free++;
    dealloc(mem);
}

void Pool::dealloc (void* mem) {
    if (tracing) {
        Log->d("For class %s, deallocating %p padding=%d\n", name, mem, padding);
    }
    ASSERT(mem != NULL);
    if (padding == 0) {
        FREE(mem);
        return;
    }
    uint**  aligned = (uint**)mem;
    uint* unaligned = aligned[-1];
    if (tracing) {
        Log->d("For class %s, deallocating unaligned %p from aligned %p\n", name, unaligned, aligned);
    }
    FREE(unaligned);
}
    
void Pool::freePush (void* mem) {
    freeCount++;
    if (tracing) {
        Log->d("For class %s, recycling %p with %d now in pool, next is %p\n", name, mem, freeCount, freePtr);
    }
    *(void**)mem = freePtr;
    freePtr = mem;
    ASSERT(freePtr != NULL);
}

void* Pool::freePop () {
    freeCount--;
    void* ptr = freePtr;
    freePtr = *(void**)ptr;
    if (tracing) {
        Log->d("For class %s, reusing %p with %d remaining in pool, next is %p\n", name, ptr, freeCount, freePtr);
    }
    ASSERT(freePtr != NULL || freeCount == 0);
    memset(ptr, 0, size);
    return ptr;
}

// this dumps pool statistics to instructionOutput
void Pool::cleanupPools () {
    PoolLog = NEW Logger("poolLog", 
                         "", 
                         "pool.sim,stderr:e", 
                         "end of program Pool statistics", 
                         NULL, 
                         false,
                         false);
    // Log is assigned so that ASSERT will still work in Pool code
    Log = PoolLog;
    for (uint i = 0; i < PoolIndex; i++) {
        Pool* pool = Pools[i];
        // we can't delete the Logger pool before deleting PoolLog
        if (pool == Logger::pool) {
            continue;
        }
        delete pool;
    }
    Logger::pool->log();
    Log = NULL;
    delete PoolLog;
    delete Logger::pool;
}

void Pool::startTracing () {
    tracing = true;
}
    
void Pool::stopTracing () {
    tracing = false;
}
